package com.vision.sales.sv.serv;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import ar.com.fdvs.dj.domain.DJCalculation;

import com.vision.core.cm.exception.RpcException;
import com.vision.core.sv.db.CustomSessionFactoryBean;
import com.vision.core.sv.db.mgr.DataManager;
import com.vision.core.sv.db.mgr.ISimpleCriteria;
import com.vision.core.sv.db.mgr.SimpleCriteria;
import com.vision.core.sv.serv.BaseRpcServlet;
import com.vision.core.sv.serv.util.ServletUtils;
import com.vision.core.sv.util.CollectionUtils;
import com.vision.core.sv.util.DateUtils;
import com.vision.sales.cm.data.SalesPermissions;
import com.vision.sales.cm.data.TransactionStatus;
import com.vision.sales.cm.db.data.Product;
import com.vision.sales.cm.db.data.SalesInvoice;
import com.vision.sales.sv.db.mgr.ProductManager;
import com.vision.sales.sv.db.mgr.SalesInvoiceManager;
import com.vision.sales.sv.report.OutstandingReceivablesRow;
import com.vision.sales.sv.report.Report;
import com.vision.sales.sv.report.ReportWriter;

/**
 * Servlet for printing Outstanding Receivables report.
 * 
 * This servlet is accessible via the /print/outstanding_receivables path in the URL (see web.xml)
 * 
 * GET parameters:
 * <ul>
 * 	<li>title - the title that will appear in the report
 * 	<li>date - the as of. Format: yyyyMMdd-yyyyMMdd</li>
 * 	<li>companies - optional comma-separated list of company ids indicating which companies to include. If not specified, all companies will be included.</li>
 * 	<li>customers - optional comma-separated list of customer ids indicating which customers to include. If not specified, all customers will be included.</li>
 * </ul>
 * 
 * Example:
 * <pre>/print/outstanding_receivables?title=Outstanding%20Receivables&date=20121231&companies=1,2&customers=5,10,22</pre>
 * 
 * @author Mark
 */
@Service("print_outstanding_receivables")
public class PrintOutstandingReceivablesServlet extends BaseRpcServlet {
	
	private static final long serialVersionUID = 1L;
	
	private static final Logger log = LoggerFactory.getLogger(PrintOutstandingReceivablesServlet.class);
	
	@Autowired
	@Qualifier("productManager")
	private DataManager<Product> productManager;

	@Autowired
	@Qualifier("salesInvoiceManager")
	private DataManager<SalesInvoice> invoiceManager;
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			ServletUtils.checkPermissions(SalesPermissions.ViewOutstandingReceivablesReport);
		} catch (RpcException e) {
			resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You are not authorized to perform this operation.");
			return;
		} 
		
		// Parameters --
		log.debug("Parsing parameters from URL...");
		OSReceivablesParams params = new OSReceivablesParams(req);
		if (params.error != null) {
			resp.sendError(HttpServletResponse.SC_BAD_REQUEST, params.error);
			return;
		}
		
		try {
			DateFormat fnameDateFormat = new SimpleDateFormat("yyyyMMdd");
			DateFormat titleDateFormat = new SimpleDateFormat("MMM d, yyyy");
			
			// Build the Report
			log.debug("Building report...");
			Report report = new Report();
			report.setFilename("Outstanding Receivables " + fnameDateFormat.format(params.date));
			report.setTitle("Outstanding Receivables as of " + titleDateFormat.format(params.date));
			report.setSize(params.size);
			report.setOrientation(params.orientation);

			// Columns and Grouping
			report.addStringColumn("product", "Product", 20, null);
			report.addStringColumn("description", "Description", 40, null);
			report.addDoubleColumn("receivables", "Receivables", 20, "#,##0.00", DJCalculation.SUM);
			
			// Data Source
			log.debug("Creating data source...");
			report.setDataSource(getDataSource(params));
			
			// Print and Write to Stream
			log.debug("Writing report to stream...");
			ReportWriter.write(report, resp);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "The server was unable to process the request.");
		}
	}
	
	private JRDataSource getDataSource(OSReceivablesParams params) {
		// XXX @Autowired doesn't seem to work, thus this patch
		productManager = new ProductManager();
		productManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		List<Product> products = productManager.getAll();
		
		// Invoices
		List<SalesInvoice> salesInvoices = getPostedSalesInvoices(params);
		Map<Long, List<SalesInvoice>> byProducts = CollectionUtils.group(salesInvoices, "getProductId");
		
		// Build Rows
		List<OutstandingReceivablesRow> rows = new ArrayList<OutstandingReceivablesRow>();

		for (Product product : products) {
			List<SalesInvoice> productSalesInvoices = byProducts.get(product.getId());
			double receivables = 0;
			if (productSalesInvoices != null) {
				for (SalesInvoice invoice : productSalesInvoices)
					receivables += invoice.getBalance();
			}
			
			OutstandingReceivablesRow row = new OutstandingReceivablesRow(product.getCode(), product.getDescription(), receivables);
			rows.add(row);
		}
				
		JRBeanCollectionDataSource ds = new JRBeanCollectionDataSource(rows);
		return ds;
	}

	@SuppressWarnings("unchecked")
	protected List<SalesInvoice> getPostedSalesInvoices(OSReceivablesParams params) {
		// XXX @Autowired doesn't seem to work, thus this patch
		invoiceManager = new SalesInvoiceManager();
		invoiceManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		ISimpleCriteria crit = SimpleCriteria.forClass(SalesInvoice.class);

		// Invoice Status = POSTED
		crit.eq("status", TransactionStatus.POSTED);

		// Invoice Date <= date
		crit.leq("date", DateUtils.endOfDay(params.date));

		// filter by specified Company Ids
		if (params.companyIds != null && !params.companyIds.isEmpty())
			crit.in("company.id", params.companyIds);

		// filter by specified Customer Ids
		if (params.customerIds != null && !params.customerIds.isEmpty())
			crit.in("customer.id", params.customerIds);
		
		// Only include invoices with balance > 0
		crit.gt("balance", 0d);
		
		// ascendingly sort by Date
		crit.asc("date");
		
		log.debug("Retrieving sales invoices...");
		List<SalesInvoice> invoices = invoiceManager.getTemplate().findByCriteria(crit.getDetached());
		return invoices;
	}
	
	// Inner classes %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	static class OSReceivablesParams extends PeriodSalesReportParams {
		
		public OSReceivablesParams(HttpServletRequest req) {
			super(req);
			
			setDefaultTitle("Outstanding Receivables Report");
		}
		
	}

}
